#region MIT Licence
/*
 * Copyright (C) 2014 PdfCreative
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom 
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using PdfCreative.Pdf.Graphics.TextRendering;
using PdfCreative.Pdf.Graphics.Tables;
using PdfCreative.Pdf.Writer;
using PdfCreative.Pdf.Writer.Fonts;
using PdfCreative.Pdf.Writer.Utilities;

namespace PdfCreative.Pdf.Graphics
{
    /// <summary>
    /// The Graphics class is the main interface of the PDF Graphics library. It is an easy-to-use layer over
    /// a Writer or PdfDocument and allows for a coding pattern similar to that of the .net framework
    /// Graphics class.
    /// </summary>
    public class Graphics
    {
        // Private fields
        // ==============
        #region Private fields
        private Graphics _baseGraphics;
        private Rectangle _bounds;
        private Dictionary<ScopedResource, PdfResourceReference> _documentResources;
        private StringFormat _defaultFormat;
        private List<TextFlow> _flowData;
        private TextFlowEngine _flowEngine;
        private GraphicsState _state;
        private Stack<GraphicsState> _stateStack;
        private GraphicsTarget _target;
        private List<PdfResourceReference> _targetResources;
        private PdfWriter _writer;
        #endregion

        #region Graphics properties
        private PageUnit _pageUnit;
        private float _dpiX;
        private float _scaleX;
        private float _scaleY;
        private float _dpiY;
        #endregion



        // Public properties
        // =================
        #region Public properties
        /// <summary>
        /// Retrieves the bounding box of this Graphics instance.
        /// </summary>
        public Rectangle Bounds
        {
            get
            {
                return _bounds;
            }
        }

        /// <summary>
        /// Sets or retrieves the units in which the Graphics object operates.
        /// </summary>
        public PageUnit PageUnit
        {
            get { return _pageUnit; }
            set
            {
                _pageUnit = value;
                CalculateScale();
            }
        }

        /// <summary>
        /// Retrieves a reference to Graphics object's underlying Writer.
        /// </summary>
        public PdfWriter Writer
        {
            get { return _writer; }
        }

        /// <summary>
        /// Sets or retrieves the horizontal resolution of the graphics.
        /// This must be a positive, non-zero value. This value only has an effect on drawing operations
        /// when the PageUnit is set to PageUnit.Pixels.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">The value was 0 or a negative value.</exception>
        public float DpiX
        {
            get { return _dpiX; }
            set
            {
                if (value > 0)
                {
                    _dpiX = value;
                    CalculateScale();
                }
                else
                    throw new ArgumentOutOfRangeException("Value must be larger than zero", "value");
            }
        }

        /// <summary>
        /// Sets or retrieves the vertical resolution of the graphics.
        /// This must be a positive, non-zero value. This value only has an effect on drawing operations
        /// when the PageUnit is set to PageUnit.Pixels.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">The value was 0 or a negative value.</exception>
        public float DpiY
        {
            get { return _dpiY; }
            set
            {
                if (value > 0)
                {
                    _dpiY = value;
                    CalculateScale();
                }
                else
                    throw new ArgumentOutOfRangeException("Value must be larger than zero", "value");
            }
        }
        #endregion



        // Public methods
        // ==============
        #region public methods
        /// <summary>
        /// Saves the current graphics state and opens a new state based on the current one.
        /// </summary>
        public void BeginContainer()
        {
            EnsurePage();

            _writer.PreserveGraphicsState();
            _stateStack.Push(_state);

            _state = (GraphicsState)_state.Clone();
        }

        /// <summary>
        /// Closes the Graphics object. This closes the current page on the PDF document and allows the Graphics object to finalise
        /// its work. This includes the creation of custom resources required for colour gradients.
        /// </summary>
        public void Close()
        {
            // Write the resources used in this page.
            FlushTargetResources();

            // The page is now complete. We can tell the PDF writer to output the definition of resources.
            _writer.ClosePage();
            WriteDocumentResources();

            _writer.Close();
        }

        /// <summary>
        /// Converts a source rectangle, in the currently active page units, to a rectangle expressed in points (as
        /// used in PDF documents)
        /// </summary>
        /// <param name="rect">The source rectangle</param>
        /// <returns>The rectangle, expressed in points</returns>
        public Rectangle ConvertRectangle(Rectangle rect)
        {
            rect.Scale(_scaleX, _scaleY);
            return rect;
        }

        /// <summary>
        /// Converts a source X coordinate, in the currently active page units, to a figure expressed in points (as used
        /// in PDF documents)
        /// </summary>
        /// <param name="x">the X coordinate</param>
        /// <returns>The value of x, in points</returns>
        public float ConvertXCoordinate(float x)
        {
            return x * _scaleX;
        }

        /// <summary>
        /// Converts a source Y coordinate, in the currently active page units, to a figure expressed in points (as used
        /// in PDF documents)
        /// </summary>
        /// <param name="y">the Y coordinate</param>
        /// <returns>The value of y, in points</returns>
        public float ConvertYCoordinate(float y)
        {
            return y * _scaleX;
        }

        /// <summary>
        /// Creates a new stencil with the specified dimensions.
        /// </summary>
        /// <param name="width">The width of the stencil, in page units</param>
        /// <param name="height">The height of the stencil, in page units</param>
        /// <returns>A reference to the new stencil object.</returns>
        public Stencil CreateStencil(float width, float height)
        {
            Stencil stencil = null;

            if (null != _baseGraphics)
                stencil = _baseGraphics.CreateStencil(width, height);
            else
                stencil = new Stencil(width, height, this);

            return stencil;
        } // CreateStencil()

        /// <summary>
        /// Creates a new table writer that can be used to write a table onto the page.
        /// </summary>
        /// <returns>The tableWriter instance.</returns>
        public TableWriter CreateTableWriter()
        {
            TableWriter writer = new TableWriter(this);

            return writer;
        }

        /// <summary>
        /// Draws an ellipse with the specified pen. The ellipse will fit inside the bounding box defined by the
        /// supplied rectangle.
        /// </summary>
        /// <param name="pen">The pen to draw with</param>
        /// <param name="rect">The rectangle to use as the basis for the ellipse's bounding box, in page units</param>
        /// <exception cref="ArgumentNullException">The pen is null</exception>
        public void DrawEllipse(Pen pen, Rectangle rect)
        {
            if (null == pen)
                throw new ArgumentNullException("pen");

            EnsurePage();

            UpdateState(pen);
            rect.Scale(_scaleX, _scaleY);
            _writer.DrawEllipse(new PdfRectangle(rect.Left, _bounds.Height-rect.Bottom, rect.Width, rect.Height));
        }
        
        /// <summary>
        /// Draws the specified image inside the destination rectangle, scaling the image where neccessary.
        /// </summary>
        /// <param name="image">The image to draw</param>
        /// <param name="dest">The destination rectangle, in page units</param>
        /// <exception cref="ArgumentNullException">The image is null</exception>
        public void DrawImage(Image image, Rectangle dest)
        {
            if (null == image)
                throw new ArgumentNullException("image");

            EnsurePage();

            PdfImage resource = EnsureImageResource(image);
            AddResourceToTarget(resource);

            _writer.DrawImage(
                resource, 
                dest.Left * _scaleX, 
                (_bounds.Height - dest.Bottom) * _scaleY, 
                dest.Width * _scaleX, 
                dest.Height * _scaleY);
        }

        /// <summary>
        /// Draws a line from a point defined by the first (x,y) coordinates to the second point.
        /// </summary>
        /// <param name="pen">The pen to draw the line in.</param>
        /// <param name="x1">The x coordinate of the starting point, in page units</param>
        /// <param name="y1">The y coordinate of the starting point, in page units</param>
        /// <param name="x2">The x coordinate of the end point, in page units</param>
        /// <param name="y2">The y coordinate of the end point, in page units</param>
        /// <exception cref="ArgumentNullException">The supplied pen is null</exception>
        public void DrawLine(Pen pen, float x1, float y1, float x2, float y2)
        {
            if (null == pen)
                throw new ArgumentNullException("pen");

            EnsurePage();

            UpdateState(pen);
            _writer.DrawLine(x1 * _scaleX, (_bounds.Height - y1) * _scaleY, x2 * _scaleX, (_bounds.Height - y2) * _scaleY);
        }

        /// <summary>
        /// Draws a path using the specified pen.
        /// </summary>
        /// <param name="pen">The pen to draw with</param>
        /// <param name="path">The path to draw</param>
        /// <exception cref="ArgumentNullException">The supplied pen or path is null</exception>
        public void DrawPath(Pen pen, GraphicsPath path)
        {
            if (null == pen)
                throw new ArgumentNullException("pen");

            if (null == path)
                throw new ArgumentNullException("path");

            EnsurePage();

            UpdateState(pen);

            _state.UnsetCurrentPoint();

            // We ask each path element to render itself - this will result in callbacks to this graphics instance and emit the
            // necessary commands.
            foreach (PathElements.PathElement element in path.Elements)
                element.Render(this);

            _writer.WriteRawContent(" S\r\n");
        } // DrawPath

        /// <summary>
        /// Draws a path at the specified coordinates. Any coordinate in the path will be offset
        /// against these coordinates.
        /// </summary>
        /// <param name="pen">The pen to draw with</param>
        /// <param name="path">The path to draw</param>
        /// <param name="x">The x coordinate, in page units</param>
        /// <param name="y">The y coordinate, in page units</param>
        public void DrawPath(Pen pen, GraphicsPath path, float x, float y)
        {
            EnsurePage();

            this.BeginContainer();
            this.TransformTranslate(x, y);
            this.DrawPath(pen, path);
            this.EndContainer();
        }

        /// <summary>
        /// Draws a rectangle with the specified pen.
        /// </summary>
        /// <param name="pen">The pen to draw with</param>
        /// <param name="rect">The rectangle, in page units</param>
        /// <exception cref="ArgumentNullException">The supplied pen is null</exception>
        public void DrawRectangle(Pen pen, Rectangle rect)
        {
            if (null == pen)
                throw new ArgumentNullException("pen");

            EnsurePage();

            UpdateState(pen);
            rect.Scale(_scaleX, _scaleY);
            _writer.DrawRectangle(new PdfRectangle(rect.Left, _bounds.Height - rect.Bottom, rect.Width, rect.Height));
        }

        /// <summary>
        /// Draws a rectangle with the specified pen. 
        /// </summary>
        /// <param name="pen">The pen to draw with</param>
        /// <param name="left">The left coordinate of the rectangle.</param>
        /// <param name="top">The top coordinate of the rectangle.</param>
        /// <param name="width">The rectangle's width.</param>
        /// <param name="height">The rectangle's height.</param>
        /// <exception cref="ArgumentNullException">The supplied pen is null</exception>
        public void DrawRectangle(Pen pen, float left, float top, float width, float height)
        {
            if (null == pen)
                throw new ArgumentNullException("pen");

            EnsurePage();

            UpdateState(pen);
            _writer.DrawRectangle(new PdfRectangle(
                left*_scaleX, _bounds.Height - ((top + height) * _scaleY), width*_scaleX, height*_scaleY));
        }

        /// <summary>
        /// Draws a rounded rectangle with the specified pen. The rounded rectangle will have bounds specified by
        /// the coordinates and dimensions, and rounded corners with the provided radius.
        /// All values are in page units.
        /// </summary>
        /// <param name="pen">The pen to draw with</param>
        /// <param name="left">The left coordinate of the rectangle.</param>
        /// <param name="top">The top coordinate of the rectangle.</param>
        /// <param name="width">The rectangle's width.</param>
        /// <param name="height">The rectangle's height.</param>
        /// <param name="radius">The radius of the rounded corners</param>
        /// <exception cref="ArgumentNullException">The supplied pen is null</exception>
        public void DrawRoundedRectangle(Pen pen, float left, float top, float width, float height, float radius)
        {
            DrawRoundedRectangle(pen, new Rectangle(left, top, width, height), radius);
        }

        /// <summary>
        /// Draws a rounded rectangle with the specified pen. The rounded rectangle will have bounds specified by
        /// the rectangle, and rounded corners with the provided radius.
        /// </summary>
        /// <param name="pen">The pen to draw with</param>
        /// <param name="rect">The rectangle, in page units</param>
        /// <param name="radius">The radius of the rounded corners</param>
        /// <exception cref="ArgumentNullException">The supplied pen is null</exception>
        public void DrawRoundedRectangle(Pen pen, Rectangle rect, float radius)
        {
            if (null == pen)
                throw new ArgumentNullException("pen");

            EnsurePage();

            UpdateState(pen);

            EmitRoundedRectangle(rect, radius);

            _writer.WriteRawContent(" S\r\n");
        }

        /// <summary>
        /// Draws the stencil at the specified location.
        /// </summary>
        /// <param name="stencil">The stencil to draw</param>
        /// <param name="x">The x coordinate, in page units</param>
        /// <param name="y">The y coordinate, in page units</param>
        /// <exception cref="ArgumentNullException">The stencil argument is a Null reference</exception>
        public void DrawStencil(Stencil stencil, float x, float y)
        {
            if (null == stencil)
                throw new ArgumentNullException("stencil");

            EnsurePage();

            // We make sure that the stencil has been added as a resource to the document.
            PdfResourceReference resource = EnsureDocumentResource(stencil, "XObject");

            // We add the stencil resource to the target.
            AddResourceToTarget(resource);

            // Finally, we draw the stencil.
            _writer.WriteRawContent("q\r\n");
            _writer.WriteRawContent(String.Format("\t{2:F2} 0 0 {3:F2} {0:F2} {1:F2} cm", x*_scaleX, (_bounds.Height - y - stencil.Height)*_scaleY, 1, 1));
            _writer.WriteRawContent(String.Format("\t/{0} Do\r\n", resource.Identifier));
            _writer.WriteRawContent("Q\r\n");

        } // DrawStencil()

        /// <summary>
        /// Draws the text at the specified location, using the brush specified. The text will be drawn using the
        /// indicated font and font size.
        /// The font size is expressed in points, regardless of the current page unit setting.
        /// The text is not flown; when text reaches the end of the page, it is clipped. No provision is made for
        /// line height or font descent. To render text across multiple lines, use a DrawString() overload that
        /// includes a bounding box.
        /// </summary>
        /// <param name="brush">The brush to draw with.</param>
        /// <param name="text">The text to draw.</param>
        /// <param name="font">The font to use</param>
        /// <param name="size">The size of the font, in points</param>
        /// <param name="x">The x coordinate, in page units</param>
        /// <param name="y">The y coordinate, in page units</param>
        /// <exception cref="ArgumentNullException">Either the brush or font reference is null</exception>
        public void DrawString(Brush brush, string text, Font font, float size, float x, float y)
        {
            if (null == brush)
                throw new ArgumentNullException("brush");
            if (null == font)
                throw new ArgumentNullException("font");

            EnsurePage();

            UpdateState(brush);
            PdfFont resource = EnsureFontResource(font);
            AddResourceToTarget(resource);

            _writer.DrawText(text, resource, size, x*_scaleX, (_bounds.Height-y-size)*_scaleY);
        }

        /// <summary>
        /// Draws the text at the specified location, using the brush specified. The text will be drawn using the
        /// indicated font and font size, and will fit the specified bounding box. Text that does not fit entirely
        /// inside the bounding box will be clipped.
        /// The font size is expressed in points, regardless of the current page unit setting.
        /// </summary>
        /// <param name="brush">The brush to draw with.</param>
        /// <param name="text">The text to draw.</param>
        /// <param name="font">The font to use</param>
        /// <param name="size">The size of the font, in points (regardless of the current page unit</param>
        /// <param name="bounds">The bounding box to draw the text in. This box is expressed in page units</param>
        /// <exception cref="ArgumentNullException">Eiter the brush, font or format is null</exception>
        public void DrawString(Brush brush, string text, Font font, float size, Rectangle bounds)
        {
            DrawString(brush, text, font, size, bounds, _defaultFormat);
        }

        /// <summary>
        /// Draws the text at the specified location, using the brush specified. The text will be drawn using the
        /// indicated font and font size, and will fit the specified bounding box. Text that does not fit entirely
        /// inside the bounding box will be clipped according to trimming rules specified in the format.
        /// The text will be aligned within the bounding box according to the format.
        /// The font size is expressed in points, regardless of the current page unit setting.
        /// The bounding box is expressed in the page units.
        /// </summary>
        /// <param name="brush">The brush to draw with.</param>
        /// <param name="text">The text to draw.</param>
        /// <param name="font">The font to use</param>
        /// <param name="size">The size of the font, in points (regardless of the current page unit</param>
        /// <param name="bounds">The bounding box to draw the text in. This box is expressed in page units</param>
        /// <param name="format">The string format</param>
        /// <exception cref="ArgumentNullException">Eiter the brush, font or format is null</exception>
        public void DrawString(Brush brush, string text, Font font, float size, Rectangle bounds, StringFormat format)
        {
            if (null == brush)
                throw new ArgumentNullException("brush");
            if (null == font)
                throw new ArgumentNullException("font");
            if (null == format)
                throw new ArgumentNullException("format");

            EnsurePage();

            DrawStyledText(
                new StyledText(text, brush, font, size*_scaleX),
                bounds,
                format);

        } // DrawString

        /// <summary>
        /// Draws the styled text within the supplied bounding box. The styled text consists of in-line text
        /// elements that provide a font, size and brush.
        /// Text that does not fit entirely inside the bounding box will be clipped.
        /// The text will be aligned within the bounding box according to the format. The bounding box is expressed
        /// in the current page units.
        /// </summary>
        /// <param name="text">The styled text to draw.</param>
        /// <param name="bounds">The bounding box to draw the text in. This box is expressed in page units</param>
        /// <param name="format">The string format</param>
        /// <exception cref="ArgumentNullException">Either the text or format is null</exception>
        public void DrawStyledText(StyledText text, Rectangle bounds, StringFormat format)
        {
            if (null == text)
                throw new ArgumentNullException("text");
            if (null == format)
                throw new ArgumentNullException("format");

            EnsurePage();

            // The bounds are in page units so we need to convert them to points before flowing the text.
            bounds.Scale(_scaleX, _scaleY);
            PdfRectangle pdfBounds = new PdfRectangle(bounds.Left, _bounds.Height - bounds.Bottom, bounds.Width, bounds.Height);

            // We flow the styled text inside the bounding box. The result will be a data structure that
            // contains lines of text data. Each line holds information on a set of text ranges that are to be
            // rendered. The text ranges span one or more text styles.
            _flowData.Clear();
            _flowEngine.Start(pdfBounds, format);
            foreach (InlineText inline in text.Inlines) _flowEngine.AddSection(inline);
            _flowEngine.Finalise();

            // Render the lines
            RenderStyledText(pdfBounds, format);
        } // DrawStyledText

        /// <summary>
        /// Draws a table at the specified position.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="left">The left position of the table.</param>
        /// <param name="top">The top position of the table.</param>
        /// <exception cref="ArgumentNullException">The table cannot be a Null reference.</exception>
        public void DrawTable(Table table, float left, float top)
        {
            this.CreateTableWriter().WriteTable(table, left, top);
        }

        /// <summary>
        /// Embeds a PDF document inside the target document.
        /// </summary>
        /// <param name="path">The path to the PDF document.</param>
        /// <exception cref="ArgumentNullException">The path cannot be a Null reference.</exception>
        public void EmbedDocument(string path)
        {
            if (null == path) throw new ArgumentNullException("path");

            // Close the current page.
            FinalisePage();

            _writer.AppendPdfDocument(path);
        }

        /// <summary>
        /// Restores a graphics state from the stack. If there are no states on the stack, this method does nothing.
        /// </summary>
        public void EndContainer()
        {
            if (_stateStack.Count > 0)
            {
                _state = _stateStack.Pop();
                _writer.RestoreGraphicsState();
            }
        }

        /// <summary>
        /// Fills an ellipse with the specified brush. The ellipse will fit inside the bounding box defined by the
        /// specified rectangle.
        /// </summary>
        /// <param name="brush">The brush to paint with.</param>
        /// <param name="rect">The rectangle to use as the ellipse's bounding box, in page units</param>
        /// <exception cref="ArgumentNullException">The brush reference is null</exception>
        public void FillEllipse(Brush brush, Rectangle rect)
        {
            if (null == brush)
                throw new ArgumentNullException("brush");

            EnsurePage();

            UpdateState(brush);
            rect.Scale(_scaleX, _scaleY);
            _writer.FillEllipse(new PdfRectangle(rect.Left, _bounds.Height - rect.Bottom, rect.Width, rect.Height));            
        }

        /// <summary>
        /// Fills a path using the specified brush.
        /// </summary>
        /// <param name="brush">The brush to paint with.</param>
        /// <param name="path">The path to draw</param>
        /// <exception cref="ArgumentNullException">The supplied brush or path is null</exception>
        public void FillPath(Brush brush, GraphicsPath path)
        {
            if (null == brush)
                throw new ArgumentNullException("brush");

            if (null == path)
                throw new ArgumentNullException("path");

            EnsurePage();

            UpdateState(brush);

            _state.UnsetCurrentPoint();

            // We ask each path element to render itself - this will result in callbacks to this graphics instance and emit the
            // necessary commands.
            foreach (PathElements.PathElement element in path.Elements)
                element.Render(this);

            _writer.WriteRawContent(" h f\r\n");
        } // FillPath

        /// <summary>
        /// Fills a path at the specified coordinates. Any coordinate in the path will be offset
        /// against these coordinates.
        /// </summary>
        /// <param name="brush">The brush to paint with.</param>
        /// <param name="path">The path to draw</param>
        /// <param name="x">The x coordinate, in page units</param>
        /// <param name="y">The y coordinate, in page units</param>
        /// <exception cref="ArgumentNullException">The supplied brush or path is null</exception>
        public void FillPath(Brush brush, GraphicsPath path, float x, float y)
        {
            EnsurePage();

            this.BeginContainer();
            this.TransformTranslate(x, y);
            this.FillPath(brush, path);
            this.EndContainer();
        }

        /// <summary>
        /// Fills a rectangle with the specified brush.
        /// </summary>
        /// <param name="brush">The brush to paint with.</param>
        /// <param name="rect">The rectangle's bounding box, in page units</param>
        /// <exception cref="ArgumentNullException">The brush reference is null</exception>
        public void FillRectangle(Brush brush, Rectangle rect)
        {
            if (null == brush)
                throw new ArgumentNullException("brush");

            EnsurePage();

            UpdateState(brush);
            rect.Scale(_scaleX, _scaleY);
            _writer.FillRectangle(new PdfRectangle(rect.Left, _bounds.Height - rect.Bottom, rect.Width, rect.Height));
        }

        /// <summary>
        /// Fills a rectangle with the specified brush.
        /// </summary>
        /// <param name="brush">The brush to paint with.</param>
        /// <param name="left">The left coordinate of the rectangle.</param>
        /// <param name="top">The top coordinate of the rectangle.</param>
        /// <param name="width">The rectangle's width.</param>
        /// <param name="height">The rectangle's height.</param>
        /// <exception cref="ArgumentNullException">The brush reference is null</exception>
        public void FillRectangle(Brush brush, float left, float top, float width, float height)
        {
            if (null == brush)
                throw new ArgumentNullException("brush");

            EnsurePage();

            UpdateState(brush);
            _writer.FillRectangle(new PdfRectangle(
                left * _scaleX, _bounds.Height - ((top + height) * _scaleY), width * _scaleX, height * _scaleY));
        }

        /// <summary>
        /// Fills a rounded rectangle with the specified brush. The rounded rectangle will have bounds specified by
        /// the coordinates and dimensions, and rounded corners with the provided radius.
        /// All values are in page units.
        /// </summary>
        /// <param name="brush">The brush to paint with.</param>
        /// <param name="left">The left coordinate of the rectangle.</param>
        /// <param name="top">The top coordinate of the rectangle.</param>
        /// <param name="width">The rectangle's width.</param>
        /// <param name="height">The rectangle's height.</param>
        /// <param name="radius">The radius of the rounded corners</param>
        /// <exception cref="ArgumentNullException">The brush reference is null</exception>
        public void FillRoundedRectangle(Brush brush, float left, float top, float width, float height, float radius)
        {
            FillRoundedRectangle(brush, new Rectangle(left, top, width, height), radius);
        }

        /// <summary>
        /// Fills a rounded rectangle with the specified brush. The rounded rectangle will have bounds specified by
        /// the rectangle, and rounded corners with the provided radius.
        /// </summary>
        /// <param name="brush">The brush to paint with.</param>
        /// <param name="rect">The rectangle, in page units</param>
        /// <param name="radius">The radius of the rounded corners</param>
        /// <exception cref="ArgumentNullException">The brush reference is null</exception>
        public void FillRoundedRectangle(Brush brush, Rectangle rect, float radius)
        {
            if (null == brush)
                throw new ArgumentNullException("brush");

            EnsurePage();

            UpdateState(brush);

            EmitRoundedRectangle(rect, radius);
            
            _writer.WriteRawContent(" f\r\n");
        }

        /// <summary>
        /// Measures the size of a text string, using a specified font and size.
        /// The font size is expressed in points, regardless of the current page unit setting.
        /// The text is not flown; the size of the text is for a single line containing all the text.
        /// To measure text across multiple lines, 
        /// use a MeasureString() overload that includes a set width.
        /// </summary>
        /// <param name="text">The text to draw.</param>
        /// <param name="font">The font to use</param>
        /// <param name="size">The size of the font, in points</param>
        /// <exception cref="ArgumentNullException">The font reference is a Null reference.</exception>
        public Rectangle MeasureString(string text, Font font, float size)
        {
            if (null == font)
                throw new ArgumentNullException("font");

            return MeasureString(text, font, size, float.PositiveInfinity, _defaultFormat);

        }

        /// <summary>
        /// Measures the size of a text string, using a specified font and size.
        /// The font size is expressed in points, regardless of the current page unit setting.
        /// The text is not flown; the size of the text is for a single line containing all the text.
        /// To measure text across multiple lines, 
        /// use a MeasureString() overload that includes a set width.
        /// The specified string format can be used to control character and word spacing, and text ratio.
        /// </summary>
        /// <param name="text">The text to draw.</param>
        /// <param name="font">The font to use</param>
        /// <param name="size">The size of the font, in points</param>
        /// <param name="format">The string format</param>
        /// <exception cref="ArgumentNullException">The font reference is a Null reference.</exception>
        public Rectangle MeasureString(string text, Font font, float size, StringFormat format)
        {
            if (null == font)
                throw new ArgumentNullException("font");

            return MeasureString(text, font, size, float.PositiveInfinity, format);

        }

        /// <summary>
        /// Measures the size of a bounded text string, using a specified font and bounding box width.
        /// The font size is expressed in points, regardless of the current page unit setting.
        /// The text is flown inside a bounding box whose width is specified by the width parameter.
        /// This method returns a rectangle that represents the smallest bounding box for the given text
        /// and width.
        /// Line height and font descent is taken into account - i.e., the text is flown as if it was drawn
        /// inside a bounding box of the specified width and suitable height.
        /// </summary>
        /// <param name="text">The text to draw.</param>
        /// <param name="font">The font to use</param>
        /// <param name="size">The size of the font, in points</param>
        /// <param name="width">The width of the bounding box, in page units.</param>
        /// <exception cref="ArgumentNullException">The font reference is a Null reference.</exception>
        public Rectangle MeasureString(string text, Font font, float size, float width)
        {
            if (null == font)
                throw new ArgumentNullException("font");

            return MeasureString(text, font, size, width, _defaultFormat);
        }

        /// <summary>
        /// Measures the size of a bounded text string, using a specified font and bounding box width.
        /// The font size is expressed in points, regardless of the current page unit setting.
        /// The text is flown inside a bounding box whose width is specified by the width parameter.
        /// This method returns a rectangle that represents the smallest bounding box for the given text
        /// and width.
        /// Line height and font descent is taken into account - i.e., the text is flown as if it was drawn
        /// inside a bounding box of the specified width and suitable height.
        /// The specified string format instance controls further formatting such as word spacing.
        /// </summary>
        /// <param name="text">The text to draw.</param>
        /// <param name="font">The font to use</param>
        /// <param name="size">The size of the font, in points</param>
        /// <param name="width">The width of the bounding box, in page units.</param>
        /// <param name="format">The string format</param>
        /// <exception cref="ArgumentNullException">The font reference or format is a Null reference.</exception>
        public Rectangle MeasureString(string text, Font font, float size, float width, StringFormat format)
        {
            if (null == font)
                throw new ArgumentNullException("font");
            if (null == format)
                throw new ArgumentNullException("format");

            return MeasureStyledText(
                new StyledText(text, Rgb.BlackBrush, font, size * _scaleX),
                width,
                format);

        }

        /// <summary>
        /// Measures the size of a styled text.
        /// The text is not flown; the size of the text is for a single line containing all the text.
        /// To measure text across multiple lines, 
        /// use a MeasureStyledText() overload that includes a set width.
        /// This method returns a rectangle that represents the smallest bounding box for the given text
        /// and width.
        /// The specified string format instance controls further formatting such as word spacing.
        /// </summary>
        /// <param name="text">The styled text to draw.</param>
        /// <param name="format">The string format</param>
        /// <exception cref="ArgumentNullException">The text or format is a Null reference.</exception>
        public Rectangle MeasureStyledText(StyledText text, StringFormat format)
        {
            return MeasureStyledText(text, float.PositiveInfinity, format);
        }

        /// <summary>
        /// Measures the size of a styled text using the specified bounding box width.
        /// The text is flown inside a bounding box whose width is specified by the width parameter.
        /// This method returns a rectangle that represents the smallest bounding box for the given text
        /// and width.
        /// Line height and font descent is taken into account - i.e., the text is flown as if it was drawn
        /// inside a bounding box of the specified width and suitable height.
        /// The specified string format instance controls further formatting such as word spacing.
        /// </summary>
        /// <param name="text">The styled text to draw.</param>
        /// <param name="width">The width of the bounding box, in page units.</param>
        /// <param name="format">The string format</param>
        /// <exception cref="ArgumentNullException">The text or format is a Null reference.</exception>
        public Rectangle MeasureStyledText(StyledText text, float width, StringFormat format)
        {
            if (null == text)
                throw new ArgumentNullException("text");

            if (null == format)
                throw new ArgumentNullException("format");

            // The bounds are in page units so we need to convert them to points before flowing the text.
            PdfRectangle pdfBounds = new PdfRectangle(0, 0, width*_scaleX, float.PositiveInfinity);

            // We flow the styled text inside the bounding box. The result will be a data structure that
            // contains lines of text data. Each line holds information on a set of text ranges that are to be
            // rendered. The text ranges span one or more text styles.
            _flowData.Clear();
            _flowEngine.Start(pdfBounds, format);
            foreach (InlineText inline in text.Inlines) _flowEngine.AddSection(inline);
            _flowEngine.Finalise();

            // The bounding box is defined by the widest line and the total of all line heights.
            return CalculateFlowBox();
        }

        /// <summary>
        /// Opens a new page. The page will have the same size as the current page.
        /// </summary>
        public void NewPage()
        {
            NewPage(_bounds);
        }

        /// <summary>
        /// Opens a new page with the specified width and height.
        /// </summary>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <exception cref="ArgumentOutOfRangeException">The width or height must be greater than 0.</exception>
        public void NewPage(float width, float height)
        {
            NewPage(new Rectangle(0, 0, width, height));
        }

        /// <summary>
        /// Opens a new page with the specified bounds. Use the StandardPages class to obtain
        /// a standard page format.
        /// </summary>
        /// <param name="bounds">The page bounds.</param>
        /// <exception cref="ArgumentOutOfRangeException">The width or height must be greater than 0.</exception>
        public void NewPage(Rectangle bounds)
        {
            NewPage(bounds, Orientation.Portrait);
        }

        /// <summary>
        /// Opens a new page with the specified bounds. Use the StandardPages class to obtain
        /// a standard page format.
        /// The page will be displayed in PDF viewers according to the 'orientation' parameter.
        /// A page's display orientation does not affect its coordinate space - it only affects its
        /// appearance in a viewer. Use it to fine-tune the orientation of landscape pages for
        /// on-screen viewing or printing.
        /// </summary>
        /// <param name="bounds">The page bounds.</param>
        /// <param name="orientation">The display orientation.</param>
        /// <exception cref="ArgumentOutOfRangeException">The width or height must be greater than 0.</exception>
        public void NewPage(Rectangle bounds, Orientation orientation)
        {
            // Close the current page.
            FinalisePage();

            // Open a new one and set up this instance and the underlying writer.
            _writer.OpenPage(new PdfRectangle(0, 0, bounds.Width, bounds.Height), orientation);
            _writer.OpenContentStream();

            SetupWriter(new Rectangle(0, 0, bounds.Width, bounds.Height));
            
            // Reset the graphics state.
            _stateStack.Clear();
            _state = new GraphicsState(Writer);
        }

        /// <summary>
        /// Re-orients the page. This rotates the page by 90, 180 or 270 degrees clockwise or counter-clockwise.
        /// This convenience method combines rotation and translation transformations on the current coordinate space.
        /// 
        /// Please note that this reorientation does not alter the size and shape of the current page, nor does it alter the way
        /// the page is displayed in PDF viewers; it only adjusts the coordinate space.
        /// 
        /// Calling this method consecutively does not result in correct rotation; preserve the graphics state before
        /// calling Reorient() the first time, and restore the graphics state before calling it again.
        /// </summary>
        /// <param name="direction">The direction in which to rotate</param>
        public void Reorient(Reorientation direction)
        {
            EnsurePage();

            PdfRectangle mediaBox = _writer.CurrentPage.MediaBox;

            switch (direction)
            {
                case Reorientation.Clockwise90Degrees:
                    this.TransformRotate(-(float)(Math.PI / 2f));
                    this.TransformTranslate(-mediaBox.Height, mediaBox.Height- mediaBox.Width);
                    break;

                case Reorientation.CounterClockwise90Degrees:
                    this.TransformRotate((float)(Math.PI / 2f));
                    this.TransformTranslate(0, mediaBox.Height);
                    break;

                case Reorientation.Degrees180:
                    this.TransformRotate((float)(Math.PI));
                    this.TransformTranslate(-mediaBox.Width, mediaBox.Height);
                    break;
                    
            }
        } // Rotate90

        /// <summary>
        /// Sets the clipping mask to the specified path.
        /// </summary>
        /// <param name="path">The path</param>
        /// <exception cref="ArgumentNullException">The supplied path is null</exception>
        public void SetClip(GraphicsPath path)
        {
            if (null == path)
                throw new ArgumentNullException("path");

            EnsurePage();

            _state.UnsetCurrentPoint();

            // We ask each path element to render itself - this will result in callbacks to this graphics instance and emit the
            // necessary commands.
            foreach (PathElements.PathElement element in path.Elements)
                element.Render(this);

            _writer.WriteRawContent(" h W n\r\n");
        } // SetClip()

        /// <summary>
        /// Sets the clipping mask to the specified path.
        /// </summary>
        /// <param name="path">The path</param>
        /// <param name="x">The X coordinate to start the clip path at.</param>
        /// <param name="y">The Y coordinate to start the clip path at.</param>
        /// <exception cref="ArgumentNullException">The supplied path is null</exception>
        public void SetClip(GraphicsPath path, float x, float y)
        {
            EnsurePage();

            this.TransformTranslate(x, y);
            this.SetClip(path);
            this.TransformTranslate(-x, -y);
        }

        /// <summary>
        /// Sets the clipping mask to the specified rectangle.
        /// </summary>
        /// <param name="rect">The rectangle</param>
        public void SetClip(Rectangle rect)
        {
            EnsurePage();
    
            _writer.WriteRawContent(String.Format("{0:F2} {1:F2} {2:F2} {3:F2} re W n\r\n", rect.Left, _bounds.Height - rect.Bottom, rect.Width, rect.Height));
        }

        /// <summary>
        /// Sets the clipping mask to the specified string. This will cause further graphics to be limited to the
        /// areas covered by the string's characters.
        /// </summary>
        /// <param name="text">The text to draw.</param>
        /// <param name="font">The font to use</param>
        /// <param name="size">The size of the font, in points</param>
        /// <param name="x">The x coordinate, in page units</param>
        /// <param name="y">The y coordinate, in page units</param>
        /// <exception cref="ArgumentNullException">The font reference is null</exception>
        public void SetClip(string text, Font font, float size, float x, float y)
        {
            if (null == font)
                throw new ArgumentNullException("font");

            EnsurePage();

            PdfFont resource = EnsureFontResource(font);
            AddResourceToTarget(resource);

            _writer.SetTextRenderingMode(PdfTextRenderingMode.ClipOnly);
            _writer.DrawText(text, resource, size, x * _scaleX, (_bounds.Height - y - size) * _scaleY);
            _writer.SetTextRenderingMode(PdfTextRenderingMode.Fill);
        }
        
        /// <summary>
        /// Transforms the current coordinate space using the specified matrix.
        /// The part of the matrix that affects the coordinate space is of size 3x2. If the matrix specified is
        /// smaller than that, the missing elements will have value 0. If the matrix is larger than that, its
        /// contents will be truncated before they are applied; this does not affect the matrix instance itself.
        /// </summary>
        /// <param name="matrix">The matrix to use</param>
        /// <exception cref="ArgumentNullException">The matrix can not be null.</exception>
        public void Transform(Matrix matrix)
        {
            EnsurePage();

            Matrix transformation = new Matrix(3, 2);
            transformation.Add(matrix);

            _writer.WriteRawContent(String.Format(
                "{0:F4} {1:F4} {2:F4} {3:F4} {4:F4} {5:F4} cm\r\n",
                transformation[0, 0],
                transformation[0, 1],
                transformation[1, 0],
                transformation[1, 1],
                transformation[2, 0],
                transformation[2, 1]));

            _state.Update( transformation.Multiply(_state.CoordinateSpace) );
        } // Transform()

        /// <summary>
        /// Performs a transformation that rotates the current coordinate space.
        /// </summary>
        /// <param name="angle">The angle, in radians</param>
        public void TransformRotate(float angle)
        {
            EnsurePage();

            _state.CoordinateSpace.TransformTranslate(0, _bounds.Height);
            _state.CoordinateSpace.TransformRotate(angle);
            _state.CoordinateSpace.TransformTranslate(0, -_bounds.Height);

            _writer.TransformTranslate(0, _bounds.Height);
            _writer.TransformRotate(angle);
            _writer.TransformTranslate(0, -_bounds.Height);
        }

        /// <summary>
        /// Performs a transformation that scales the current coordinate space.
        /// </summary>
        /// <param name="horizontal">The horizontal scaling factor</param>
        /// <param name="vertical">The vertical scaling factor</param>
        public void TransformScale(float horizontal, float vertical)
        {
            EnsurePage();

            _state.CoordinateSpace.TransformTranslate(0, _bounds.Height);
            _state.CoordinateSpace.TransformScale(horizontal, vertical);
            _state.CoordinateSpace.TransformTranslate(0, -_bounds.Height);

            _writer.TransformTranslate(0, _bounds.Height);
            _writer.TransformScale(horizontal, vertical);
            _writer.TransformTranslate(0, -_bounds.Height);
        }

        /// <summary>
        /// Performs a transformation that skews the current coordinate space.
        /// </summary>
        /// <param name="radiansX">The angle, in radians, to skew the X axis by</param>
        /// <param name="radiansY">The angle, in radians, to skew the Y axis by</param>
        public void TransformSkew(float radiansX, float radiansY)
        {
            EnsurePage();

            _state.CoordinateSpace.TransformTranslate(0, _bounds.Height);
            _state.CoordinateSpace.TransformSkew(radiansX, radiansY);
            _state.CoordinateSpace.TransformTranslate(0, -_bounds.Height);

            _writer.TransformTranslate(0, _bounds.Height);
            _writer.TransformSkew(radiansX, radiansY);
            _writer.TransformTranslate(0, -_bounds.Height);
        }

        /// <summary>
        /// Performs a transformation that translates the current coordinate space.
        /// </summary>
        /// <param name="xoff">The translation along the x axis, in page units</param>
        /// <param name="yoff">The translation along the y axis, in page units</param>
        public void TransformTranslate(float xoff, float yoff)
        {
            EnsurePage();

            _state.CoordinateSpace.TransformTranslate(xoff * _scaleX, -yoff * _scaleY);
            _writer.TransformTranslate(xoff*_scaleX, -yoff*_scaleY);
        }
        #endregion



        // Event handlers
        // ==============
        #region Writer events
        /// <summary>
        /// The Writer requests a custom resource be written to its PDF stream.
        /// If this is a resource that the graphics object knows about (and didn't write to the PDF stream itself) then
        /// it will do so now.
        /// </summary>
        /// <param name="reference"></param>
        void Writer_WriteCustomResource(CustomResourceReference reference)
        {
            // Find the resource and write it to the document.
            foreach (KeyValuePair<ScopedResource, PdfResourceReference> documentResource in _documentResources)
                if (documentResource.Value == reference)
                {
                    // Write the resource and remove it from the list.
                    documentResource.Key.Resource.WriteResource(
                        this,
                        this.Writer,
                        documentResource.Value,
                        documentResource.Key);

                    _documentResources.Remove(documentResource.Key);
                    break;
                }
        }
        #endregion

        #region TextFlowEngine events
        /// <summary>
        /// A new line was flowed by the engine. We add it to the list.
        /// </summary>
        /// <param name="line">The new line</param>
        private void FlowEngine_Flow(TextFlow line)
        {
            _flowData.Add(line);
        }

        #endregion



        // Internal implementation
        // =======================
        #region Internal implementation
        /// <summary>
        /// Emits code that defines a line.
        /// </summary>
        /// <param name="x1">The x coordinate of the curve's starting point</param>
        /// <param name="y1">The y coordinate of the curve's starting point</param>
        /// <param name="x1c">The x coordinate of the curve's first control point</param>
        /// <param name="y1c">The y coordinate of the curve's first control point</param>
        /// <param name="x2c">The x coordinate of the curve's second control point</param>
        /// <param name="y2c">The y coordinate of the curve's second control point</param>
        /// <param name="x2">The x coordinate of the curve's end point</param>
        /// <param name="y2">The y coordinate of the curve's end point</param>
        internal void EmitBezierCurve(float x1, float y1, float x1c, float y1c, float x2c, float y2c, float x2, float y2)
        {
            // We see if the current point is one of the line's end points. If so, we extend the path by drawing to the other end of
            // the line. Otherwise, we start a new line altogether.
            if (_state.IsCurrentPoint(x1, y1))
            {
                _writer.WriteRawContent(String.Format(
                    "{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c ", x1c * _scaleX, (_bounds.Height - y1c) * _scaleY, x2c * _scaleX, (_bounds.Height - y2c) * _scaleY, x2 * _scaleX, (_bounds.Height - y2) * _scaleY));

                _state.SetCurrentPoint(x2, y2);
            }
            else if (_state.IsCurrentPoint(x2, y2))
            {
                _writer.WriteRawContent(String.Format(
                    "{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c ", x2c * _scaleX, (_bounds.Height - y2c) * _scaleY, x1c * _scaleX, (_bounds.Height - y1c) * _scaleY, x1 * _scaleX, (_bounds.Height - y1) * _scaleY));

                _state.SetCurrentPoint(x1, y1);
            }
            else
            {
                _writer.WriteRawContent(String.Format(
                    "{0:F2} {1:F2} m ", x1 * _scaleX, (_bounds.Height - y1) * _scaleY));

                _writer.WriteRawContent(String.Format(
                    "{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c ", x1c * _scaleX, (_bounds.Height - y1c) * _scaleY, x2c * _scaleX, (_bounds.Height - y2c) * _scaleY, x2 * _scaleX, (_bounds.Height - y2) * _scaleY));

                _state.SetCurrentPoint(x2, y2);
            }
        }

        /// <summary>
        /// Emits code that defines a line.
        /// </summary>
        /// <param name="x1">The x coordinate of the line's starting point.</param>
        /// <param name="y1">The y coordinate of the line's starting point.</param>
        /// <param name="x2">The x coordinate of the line's end point.</param>
        /// <param name="y2">The y coordinate of the line's end point.</param>
        internal void EmitLine(float x1, float y1,float x2, float y2)
        {
            // We see if the current point is one of the line's end points. If so, we extend the path by drawing to the other end of
            // the line. Otherwise, we start a new line altogether.
            if(_state.IsCurrentPoint(x1, y1))
            {
                _writer.WriteRawContent(String.Format(
                    "{0:F2} {1:F2} l ", x2 * _scaleX, (_bounds.Height - y2) * _scaleY));

                _state.SetCurrentPoint(x2, y2);
            }
            else if (_state.IsCurrentPoint(x2, y2))
            {
                _writer.WriteRawContent(String.Format(
                    "{0:F2} {1:F2} l ", x1 * _scaleX, (_bounds.Height - y1) * _scaleY));

                _state.SetCurrentPoint(x1, y1);
            }
            else
            {
                _writer.WriteRawContent(String.Format(
                    "{0:F2} {1:F2} m {2:F2} {3:F2} l ", x1*_scaleX, (_bounds.Height - y1)*_scaleY,
                    x2*_scaleX, (_bounds.Height - y2)*_scaleY));

                _state.SetCurrentPoint(x2, y2);
            }
        }

        /// <summary>
        /// Emits code that closes the current sub-path.
        /// </summary>
        internal void EmitPathClosure()
        {
            _writer.WriteRawContent("h ");
            _state.UnsetCurrentPoint();
        }

        /// <summary>
        /// Emits code that defines a rectangle.
        /// </summary>
        /// <param name="left">The x coordinate of the rectangle's top-left corner.</param>
        /// <param name="top">The y coordinate of the rectangle's top-left corner.</param>
        /// <param name="width">The rectangle's width.</param>
        /// <param name="height">The rectangle's height.</param>
        internal void EmitRectangle(float left, float top, float width, float height)
        {
            Rectangle rect = new Rectangle(left, top, width, height);
            rect.Scale(_scaleX, _scaleY);
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} {2:F2} {3:F2} re ", rect.Left, _bounds.Height - rect.Bottom, rect.Width, rect.Height));
        }

        /// <summary>
        /// Emits operators for the drawing, filling or defining of a rounded rectangle shape.
        /// </summary>
        /// <param name="rect">The rectangle to use as the bounding box of the rounded rectangle</param>
        /// <param name="radius">The radius of the rounded corners</param>
        internal void EmitRoundedRectangle(Rectangle rect, float radius)
        {
            rect.Scale(_scaleX, _scaleY);
            radius = radius * _scaleX;

            Rectangle inner = new Rectangle(rect.Left + radius, rect.Top + radius, rect.Width - (radius * 2), rect.Height - (radius * 2));
            float ellipseModifier = (float)(4 * ((Math.Sqrt(2) - 1) / 3f));
            float horizontalControl = radius * ellipseModifier;
            float verticalControl = radius * ellipseModifier;

            // bottom edge
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} m {2:F2} {3:F2} l ", inner.Left, _bounds.Height - rect.Bottom, inner.Right, _bounds.Height - rect.Bottom));

            // bottom right curve
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c ", inner.Right + horizontalControl, _bounds.Height - rect.Bottom, rect.Right, _bounds.Height - (inner.Bottom + verticalControl), rect.Right, _bounds.Height - inner.Bottom));

            // right edge    
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} l ", rect.Right, _bounds.Height - inner.Top));

            // top right curve
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c ", rect.Right, _bounds.Height - inner.Top + verticalControl, inner.Right + horizontalControl, _bounds.Height - rect.Top, inner.Right, _bounds.Height - rect.Top));

            // top edge
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} l ", inner.Left, _bounds.Height - rect.Top));

            // top left curve
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c ", inner.Left - horizontalControl, _bounds.Height - rect.Top, rect.Left, _bounds.Height - (rect.Top + verticalControl), rect.Left, _bounds.Height - inner.Top));

            // left edge    
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} l ", rect.Left, _bounds.Height - inner.Bottom));

            // bottom left curve
            _writer.WriteRawContent(String.Format(
                "{0:F2} {1:F2} {2:F2} {3:F2} {4:F2} {5:F2} c ", rect.Left, _bounds.Height - inner.Bottom - verticalControl, inner.Left - horizontalControl, _bounds.Height - rect.Bottom, inner.Left, _bounds.Height - rect.Bottom));

        } // EmitRoundedRectangle()

        /// <summary>
        /// Retrieves the resources used by the target (page or stencil)
        /// </summary>
        /// <returns>The resources</returns>
        internal IEnumerable<PdfResourceReference> GetTargetResources()
        {
            return _targetResources;
        }
        #endregion



        // Private implementation
        // ======================
        #region Private implementation
        /// <summary>
        /// Adds the resource to the target. If the target is a page, the resource is added using the
        /// underlying PDF writer. If the target is a stencil, the target is stored in a list instead.
        /// </summary>
        /// <param name="resource">The resource</param>
        private void AddResourceToTarget(PdfResourceReference resource)
        {
            if (!_targetResources.Contains(resource))
                _targetResources.Add(resource);
        }

        /// <summary>
        /// Calculates the bounding box of the current flow state.
        /// </summary>
        /// <returns>The bounding box.</returns>
        private Rectangle CalculateFlowBox()
        {
            // The bounding box width is the largest width of all lines.
            float width = 0;
            foreach (TextFlow flow in _flowData) width = Math.Max(flow.Width, width);

            // Its height is the cumulative height of all lines.
            float height = 0;
            foreach (TextFlow flow in _flowData) height += flow.Height;

            // Return the bounding box, scaled to page units.
            return new Rectangle(0, 0, width / _scaleX, height / _scaleY);
        }

        /// <summary>
        /// Recalculates the current drawing scale.
        /// </summary>
        private void CalculateScale()
        {
            switch (_pageUnit)
            {
                case PageUnit.Centimetres:
                    _scaleX = _scaleY = 72f / 2.54f;
                    break;

                case PageUnit.Inches:
                    _scaleX = _scaleY = 72;
                    break;

                case PageUnit.Millimetres:
                    _scaleX = _scaleY = 72f / 25.4f;
                    break;

                case PageUnit.Pixels:
                    _scaleX = 72 / _dpiX;
                    _scaleY = 72 / _dpiX;
                    break;

                case PageUnit.Points:
                    _scaleX = _scaleY = 1f;
                    break;
            }
        }

        /// <summary>
        /// Calculates the total width of all text elements in the specified line, as well as the number of words.
        /// </summary>
        /// <param name="line">The line to calculate the measures for</param>
        /// <param name="textWidth">The total width of all text elements.</param>
        /// <param name="wordCount">The number of distinct words in the line</param>
        private static void CalculateWordCount(TextFlow line, out float textWidth, out int wordCount)
        {
            textWidth = 0f;
            wordCount = 0;

            TokenType previousType = TokenType.Whitespace;
            float previousWidth = 0f;
            foreach (Token token in line.Tokens)
            {
                if (TokenType.Text == token.TokenType)
                {
                    textWidth += token.TokenWidth;
                    if (TokenType.Whitespace == previousType) wordCount++; else textWidth -= previousWidth;
                    previousWidth = token.TokenWidth;
                }
                previousType = token.TokenType;
            }
        }

        /// <summary>
        /// Creates a new document resource reference based on the specified scope and resource type.
        /// </summary>
        /// <param name="scopedResource">The resource writer to create the resource for.</param>
        /// <param name="resourceType">The type of resource to create.</param>
        private PdfResourceReference CreateDocumentResource(ScopedResource scopedResource, string resourceType)
        {
            // We store an entry for the resource that includes the current coordinate space, if applicable.
            ScopedResource resourceCopy = new ScopedResource(scopedResource);

            // We ask the PDF writer for a new custom resource based on a new object.
            PdfObjectReference resourceObject = _writer.CreateObjectReference();
            PdfResourceReference resourceRef = _writer.CreateCustomResource(resourceObject, resourceType);

            _documentResources[resourceCopy] = resourceRef;

            return resourceRef;
        }

        /// <summary>
        /// Creates a new font resource reference.
        /// </summary>
        /// <param name="scopedResource">The resource writer to create the resource for.</param>
        /// <param name="font">The font to create a resource of.</param>
        private PdfResourceReference CreateFontResource(ScopedResource scopedResource, Font font)
        {
            // We store an entry for the resource that includes the current coordinate space, if applicable.
            ScopedResource resourceCopy = new ScopedResource(scopedResource);

            // We ask the PDF writer for a new font resource.
            PdfFont fontResource = _writer.CreateFont(font.TypeName, font.IsEmbedded);

            _documentResources[resourceCopy] = fontResource;

            return fontResource;
        }

        /// <summary>
        /// Creates a new image resource reference.
        /// </summary>
        /// <param name="scopedResource">The resource writer to create the resource for.</param>
        /// <param name="image">The image to create a resource of.</param>
        private PdfResourceReference CreateImageResource(ScopedResource scopedResource, Image image)
        {
            // We store an entry for the resource that includes the current coordinate space, if applicable.
            ScopedResource resourceCopy = new ScopedResource(scopedResource);

            // We ask the PDF writer for a new font resource.
            PdfImage imageResource = _writer.CreateImage(image.Filename);

            _documentResources[resourceCopy] = imageResource;

            return imageResource;
        }

        private ScopedResource CreateScopedResource(IResourceWriter obj)
        {
            ScopedResource result = null;

            switch (obj.Scope)
            {
                case ResourceScope.Document:
                    result = new ScopedResource(obj, null, Rectangle.Empty);
                    break;

                case ResourceScope.CurrentCoordinateSpace:
                    result = new ScopedResource(obj, _state.CoordinateSpace, _bounds);
                    break;
            }

            return result;
        }

        /// <summary>
        /// Checks to see if a document resource has been added for the specified resource object,
        /// and creates one if necessary. When applicable, the current transformation matrix is
        /// considered. A resource may be created multiple times if it is tied to the
        /// transformation matrix.
        /// This method does not add the resource to content, nor does it register the resource with the
        /// current page. It registers the resource with the document so that its definition can be
        /// included when the document is finalised.
        /// </summary>
        /// <param name="obj">The resource object to add to the document.</param>
        /// <param name="type">The type of the resource</param>
        /// <returns></returns>
        private PdfResourceReference EnsureDocumentResource(IResourceWriter obj, string type)
        {
            ScopedResource scopedResource = CreateScopedResource(obj);
            
            return EnsureScopedResource(scopedResource, type);
        }

        /// <summary>
        /// Checks to see if a PDF resource has been created for the font. The resource is
        /// created if necessary.
        /// </summary>
        /// <param name="font">The font</param>
        /// <returns>The resource for the font.</returns>
        private PdfFont EnsureFontResource(Font font)
        {
            ScopedResource scopedResource = new ScopedResource(font, null, Rectangle.Empty);

            return (PdfFont) EnsureScopedResource(scopedResource, "Font");
        }

        /// <summary>
        /// Checks to see if a PDF resource has been created for the image. The resource is
        /// created if necessary.
        /// </summary>
        /// <param name="image">The image</param>
        /// <returns>The resource for the image.</returns>
        private PdfImage EnsureImageResource(Image image)
        {
            ScopedResource scopedResource = new ScopedResource(image, null, Rectangle.Empty);

            return (PdfImage)EnsureScopedResource(scopedResource, "Image");
        }

        /// <summary>
        /// Ensures a page has been opened before performing graphics operations.
        /// </summary>
        private void EnsurePage()
        {
            // If a content stream has currently been set up then we use that. Otherwise,
            // we need to create a new page.
            if (WriterState.Content != _writer.State)
                NewPage();
        }

        /// <summary>
        /// Ensures that the scoped resource has been added to the PDF document.
        /// </summary>
        /// <param name="scopedResource">The scoped resource.</param>
        /// <param name="type">The resource type.</param>
        /// <returns>The resource</returns>
        private PdfResourceReference EnsureScopedResource(ScopedResource scopedResource, string type)
        {
            PdfResourceReference resource = null;

            if (null != _baseGraphics)
                resource = _baseGraphics.EnsureScopedResource(scopedResource, type);
            else
            {
                if (false == _documentResources.TryGetValue(scopedResource, out resource))
                {
                    // Create the resource.

                    switch (type)
                    {
                        case "Font":
                            resource = CreateFontResource(scopedResource, (Font)scopedResource.Resource);
                            break;

                        case "Image":
                            resource = CreateImageResource(scopedResource, (Image)scopedResource.Resource);
                            break;

                        default:
                            resource = CreateDocumentResource(scopedResource, type);
                            break;
                    }                    
                }
            }

            return resource;
        } // EnsureDocumentResource()

        /// <summary>
        /// Escapes the specified substring of the specified text and returns it.
        /// </summary>
        /// <param name="text">The text</param>
        /// <param name="start">The index within the text at which to start.</param>
        /// <param name="length">The length of the sub-string</param>
        /// <returns>The escaped sub-string</returns>
        private static string EscapeSubstring(string text, int start, int length)
        {

            StringBuilder sb = new StringBuilder();
            
            byte[] bytes = new byte[length];
            System.Text.ASCIIEncoding.Default.GetBytes(text, start, length, bytes, 0);

            int ch;
            for (int index = 0; index < bytes.Length; index++)
            {
                ch = bytes[index];
                if (ch >= 128) sb.Append('\\').Append(Convert.ToString(ch, 8));
                else if (ch == 40 || ch == 41) sb.Append("\\0").Append(Convert.ToString((int)ch, 8));
                else if (ch == 92) sb.Append("\\").Append(Convert.ToString((int)ch, 8));
                else sb.Append((char)ch);
            }
            return sb.ToString();
        } // EscapeSubstring()

        /// <summary>
        /// Finalises the current page. This involves ensuring that all resources are flushed.
        /// This method is called by NewPage() and EmbedDocument();
        /// </summary>
        private void FinalisePage()
        {
            FlushTargetResources();
        }

        /// <summary>
        /// Flushes the list of target resources. This method adds the resources to the
        /// underlying writer and clears the list.
        /// </summary>
        private void FlushTargetResources()
        {
            // Write the resources used in this page.
            foreach (PdfResourceReference resource in _targetResources)
                _writer.AddResource(resource);

            _targetResources.Clear();
        }

        /// <summary>
        /// Renders the styled text that was flowed by the text flow engine, using the specified boundaries and text formatting object.
        /// </summary>
        /// <param name="pdfBounds">The boundaries to render in</param>
        /// <param name="format">The string format</param>
        private void RenderStyledText(PdfRectangle pdfBounds, StringFormat format)
        {
            System.Diagnostics.Debug.Assert(null != _flowData);

            if (_flowData.Count > 0)
            {
                // The string alignment determines the line position and line starting point.
                // Regardless of the vertical alignment the line position takes into account the
                // font's descent.
                float linePosition;
                switch (format.VerticalAlignment)
                {
                    case VerticalAlignment.Top:
                    default:
                        linePosition = pdfBounds.Top;
                        break;

                    case VerticalAlignment.Bottom:
                        linePosition = pdfBounds.Bottom;
                        foreach (TextFlow line in _flowData) linePosition += line.Height;
                        break;

                    case VerticalAlignment.Center:
                        float totalTextHeight = 0f;
                        foreach (TextFlow line in _flowData) totalTextHeight += line.Height;
                        linePosition = pdfBounds.Top - (pdfBounds.Height - totalTextHeight) / 2;
                        break;
                }

                // Render each line now. The start of each line depends on the horizontal alignment.
                float lineStart = pdfBounds.Left;

                // We commence writing text rendering operations.
                // The first operations specify the initial position, as well as
                // the word, character and line spacing.
                //this.BeginContainer();
                _writer.WriteRawContent("BT\r\n");

                _writer.WriteRawContent(String.Format("{0:F2} {1:F2} Td\r\n", lineStart, linePosition));

                _state.Update(format);

                // Now we draw the lines.
                // If the text if fully justified then we need to determine how many words are on a line, and distribute the
                // remaining space over the gaps between the words.
                // We then need to draw each individual word, making sure to output font information for the different tokens.
                // If the text is left-aligned, right-aligned or centered we display large chunks of text because
                // we do not need to insert custom whitespace between words.

                float horizontalOffset = 0f;        // How much to offset each line
                float lastHorizontalOffset = 0f;    // Offset of the line before
                float lastDescent = 0f;             // The descent of the previous line

                int lineEnum = 0;
                foreach (TextFlow line in _flowData)
                {
                    // The last line in the set is left-aligned if the alignment is set to fully justified.
                    HorizontalAlignment alignment = format.HorizontalAlignment;
                    if (HorizontalAlignment.FullyJustified==alignment && lineEnum == _flowData.Count - 1)
                        alignment = HorizontalAlignment.Left;

                    // Set up the line height and calculate the offset for this line.
                    switch (alignment)
                    {
                        case HorizontalAlignment.Left:
                        case HorizontalAlignment.FullyJustified:
                            horizontalOffset = 0f;
                            break;

                        case HorizontalAlignment.Right:
                            horizontalOffset = (pdfBounds.Width - line.Width);
                            break;

                        case HorizontalAlignment.Center:
                            horizontalOffset = (pdfBounds.Width - line.Width) / 2;
                            break;
                    }

                    _writer.WriteRawContent(String.Format(" {1:F2} -{0:F2} Td\r\n", line.Height + line.Descent - lastDescent, horizontalOffset - lastHorizontalOffset));

                    // If the text if fully justified then we need to determine how many words are on a line, and distribute the
                    // remaining space over the gaps between the words.
                    // We then need to draw each individual word, making sure to output font information for the different tokens.
                    if (HorizontalAlignment.FullyJustified == alignment)
                    {
                        // We go through the tokens and count the number of distinct text elements, and find the total width of all
                        // text elements.
                        float textWidth = 0f;
                        int wordCount = 0;
                        CalculateWordCount(line, out textWidth, out wordCount);
                        float whitespaceWidth = wordCount > 1 ? (pdfBounds.Width - textWidth) / (float)(wordCount - 1) : 0f;

                        // Now we go through all the tokens, and output text and whitespace.
                        InlineText previousInline = null;
                        int elementsInArray = 0;
                        int adjustedSpacing = 0; // spacing, adjusted for current font size and text ratio.
                        foreach (Token token in line.Tokens)
                        {
                            if (null != token)
                            {
                                // we are only interested in text tokens.
                                if (TokenType.Text == token.TokenType)
                                {
                                    // If this token uses a different style then we need to close the current array and update the
                                    // style.
                                    if (token.Inline != previousInline)
                                    {
                                        // Close the array. If we 
                                        if (elementsInArray > 0)
                                            _writer.WriteRawContent(String.Format("] TJ ", adjustedSpacing));

                                        // Switch to the inline's brush and make sure the font is added 
                                        // as a resource on this page before referencing it here. Also, the
                                        // spacing needs to be adjusted for the current font settings.
                                        UpdateState(token.Inline.Brush);
                                        PdfResourceReference fontResource = EnsureFontResource(token.Inline.Font);
                                        AddResourceToTarget(fontResource);
                                        _writer.WriteRawContent(String.Format("/{0} {1:F2} Tf\r\n",
                                                                              fontResource.Identifier,
                                                                              token.Inline.FontSize));
                                        adjustedSpacing =
                                            (int)-(1000 * whitespaceWidth / (token.Inline.FontSize * format.TextRatio));

                                        previousInline = token.Inline;
                                        elementsInArray = 0;
                                    }

                                    // Add a spacing instruction if this isn't the first element in the array, and an instruction for the
                                    // text token.
                                    if (0 == elementsInArray) _writer.WriteRawContent("[");
                                    _writer.WriteRawContent(String.Format(" ({0}) ",
                                                                          EscapeSubstring(token.Inline.Text, token.Start,
                                                                                          token.End - token.Start + 1)));

                                    // next token
                                    elementsInArray++;
                                } // for each text token
                                else if (elementsInArray > 0) _writer.WriteRawContent(adjustedSpacing.ToString());

                            } // If the token isn't null
                        }// for each token

                        // We need to close the current array and move to the next line.
                        if (elementsInArray > 0) _writer.WriteRawContent("] TJ T*\r\n");
                        lastDescent = line.Descent;
                    }
                    else
                    {
                        // We go through the tokens in the flow data. Whenever we find a different inline text element we plot
                        // the text with the appropriate brush and font.
                        Token firstToken = null;
                        int textLength = 0;
                        foreach (Token token in line.Tokens)
                        {
                            if (null == firstToken || token.Inline != firstToken.Inline)
                            {
                                // Draw the previous inline's text.
                                if (null != firstToken)
                                    _writer.WriteRawContent(String.Format("({0}) Tj ", EscapeSubstring(firstToken.Inline.Text, firstToken.Start, textLength)));

                                // Switch to the inline's brush and make sure the font is added 
                                // as a resource on this page before referencing it here
                                UpdateState(token.Inline.Brush);
                                PdfResourceReference fontResource = EnsureFontResource(token.Inline.Font);
                                AddResourceToTarget(fontResource);
                                _writer.WriteRawContent(String.Format("/{0} {1:F2} Tf\r\n", fontResource.Identifier, token.Inline.FontSize));

                                // this token is the first of a new text
                                firstToken = token;
                                textLength = 0;
                            }

                            // next token
                            textLength += token.End - token.Start + 1;
                        }

                        // Draw the last piece of text
                        if (null != firstToken)
                            _writer.WriteRawContent(String.Format("({0}) Tj", EscapeSubstring(firstToken.Inline.Text, firstToken.Start, textLength)));

                        // next line
                        lastHorizontalOffset = horizontalOffset;
                        lastDescent = line.Descent;
                        _writer.WriteRawContent("\r\n");
                    }

                    // Next line
                    ++lineEnum;

                } // for each line

                // Update the state - resetting the character and word spacing, and text ratio.
                _state.Update(new StringFormat());

                // This is the end of the text.
                _writer.WriteRawContent("ET\r\n");

            } // If there are any elements to draw.

        } // RenderStyledText()

        /// <summary>
        /// Sets up default values for the properties of this Graphics instance.
        /// </summary>
        private void SetupDefaults()
        {
            _dpiX = 72f;
            _dpiY = 72f;
            this.PageUnit = PageUnit.Points;
        }

        /// <summary>
        /// Sets up the writer so that it is ready to accept content instructions.
        /// </summary>
        /// <param name="targetSize">The size of the target (page or stencil).</param>
        private void SetupWriter(Rectangle targetSize)
        {
            System.Diagnostics.Debug.Assert(targetSize.Width > 0);
            System.Diagnostics.Debug.Assert(targetSize.Height > 0);

            // We remember the height of the current page.
            _bounds = new Rectangle(0, 0, targetSize.Width, targetSize.Height);

        } // SetupWriter()

        /// <summary>
        /// Updates the current graphics state so that it incorporates the specified pen's properties.
        /// </summary>
        /// <param name="pen">The pen</param>
        private void UpdateState(Pen pen)
        {
            // Update the graphics state. If the state has changed, the pen will be asked to write out the differences.
            _state.Update(pen);
        }

        /// <summary>
        /// Updates the current graphics state so that it incorporates the specified brush's properties.
        /// </summary>
        /// <param name="brush">The brush</param>
        private void UpdateState(Brush brush)
        {
            // If this brush requires a resource we find out if has already been associated with one.
            // If not, we create a resource for it, and mark it so that we can create
            // the actual resource at a later stage.
            // We also make sure that it has already been added as a resource to the current page.
            // We then update the graphics state using the pattern.
            if (true == brush.RequiresResource)
            {
                // We make sure that the brush implements the IResourceWriter interface
                if (brush is IResourceWriter)
                {
                    // We make sure that the brush has been added as a resource to the document.
                    IResourceWriter resourceWriter = (IResourceWriter)brush;
                    PdfResourceReference resource = EnsureDocumentResource(resourceWriter, "Pattern");

                    // We add the brush resource to the target.
                    AddResourceToTarget(resource);

                    // Update the graphics state
                    _state.Update(brush, resource);
                }
                else
                    throw new InvalidOperationException("The specified brush requires a resource but does not implement the IResourceWriter interface");
            }
            else
            {
                // Update the graphics state. If the state has changed, ask the brush to write out the differences.
                _state.Update(brush);
            }

        } // UpdateState()

        /// <summary>
        /// Writes the document resources to the underlying writer.
        /// </summary>
        private void WriteDocumentResources()
        {
            // Write all the resources
            foreach (KeyValuePair<ScopedResource, PdfResourceReference> documentResource in _documentResources)
                if(documentResource.Key.Resource.IsCustomResource)
                    documentResource.Key.Resource.WriteResource(
                        this, 
                        this.Writer, 
                        documentResource.Value, 
                        documentResource.Key);

            // Clear the list
            _documentResources.Clear();

        } // WriteDocumentResources()
        #endregion



        // Constructors
        // ============
        #region Public constructor
        /// <summary>
        /// Constructs a new Graphics instance that will create a PDF document at the specified
        /// path.
        /// </summary>
        /// <param name="path">The path</param>
        public Graphics(string path)
            : this(new PdfWriter(path), null, GraphicsTarget.Page, StandardPages.A4) { }

        /// <summary>
        /// Constructs a new Graphics instance that will create a PDF document at the specified path.
        /// </summary>
        /// <param name="path">The path</param>
        /// <param name="width">The width of the page, in points.</param>
        /// <param name="height">The height of the page, in point.</param>
        /// <exception cref="ArgumentOutOfRangeException">The width or height must be greater than 0.</exception>
        public Graphics(string path, float width, float height)
            : this(new PdfWriter(path), null, GraphicsTarget.Page, new Rectangle(0, 0, width, height)) { }


        /// <summary>
        /// Constructs a new Graphics instance that will create a PDF document at the specified path.
        /// The page size is indicated by the <paramref name="pageSize"/> parameter. Use the
        /// StandardPages class to obtain common page formats.
        /// </summary>
        /// <param name="path">The path</param>
        /// <param name="pageSize">The size of the page.</param>
        /// <exception cref="ArgumentOutOfRangeException">The width or height must be greater than 0.</exception>
        public Graphics(string path, Rectangle pageSize)
            : this(new PdfWriter(path), null, GraphicsTarget.Page, pageSize) { }

        /// <summary>
        /// Constructs a new Graphics instance what will create a PDF document in the specified stream.
        /// The stream must support writing.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <exception cref="ArgumentNullException">The stream is a Null reference.</exception>
        /// <exception cref="ArgumentException">The stream does not support writing.</exception>
        public Graphics(Stream stream)
            : this(new PdfWriter(stream), null, GraphicsTarget.Page, StandardPages.A4) { }

        /// <summary>
        /// Constructs a new Graphics instance what will create a PDF document in the specified stream.
        /// The stream must support writing.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="width">The width of the page, in points.</param>
        /// <param name="height">The height of the page, in point.</param>
        /// <exception cref="ArgumentNullException">The stream is a Null reference.</exception>
        /// <exception cref="ArgumentException">The stream does not support writing.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The width or height must be greater than 0.</exception>
        public Graphics(Stream stream, float width, float height)
            : this(new PdfWriter(stream), null, GraphicsTarget.Page, new Rectangle(0,0,width,height)) { }

        /// <summary>
        /// Constructs a new Graphics instance what will create a PDF document in the specified stream.
        /// The stream must support writing.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="pageSize">The size of the page.</param>
        /// <exception cref="ArgumentNullException">The stream is a Null reference.</exception>
        /// <exception cref="ArgumentException">The stream does not support writing.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The width or height must be greater than 0.</exception>
        public Graphics(Stream stream, Rectangle pageSize)
            : this(new PdfWriter(stream), null, GraphicsTarget.Page, pageSize) { }

        #endregion

        #region Private constructor
        /// <summary>
        /// Constructs a new Graphics instance that outputs to the specified writer.
        /// The target argument indicates the kind of target for painting operations -
        /// either the current page or a stencil instance.
        /// </summary>
        /// <param name="writer">The PDF writer.</param>
        /// <param name="baseGraphics">The graphics that this instance is based on (or Null if this instance
        /// is not based on any other graphics instance)</param>
        /// <param name="target">The target for graphics operations</param>
        /// <param name="targetSize">The size of the target.</param>
        /// <exception cref="ArgumentNullException">The writer can not be a Null reference.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The width or height must be greater than 0.</exception>
        internal Graphics(PdfWriter writer, Graphics baseGraphics, GraphicsTarget target, Rectangle targetSize)
        {
            if (null == writer) throw new ArgumentNullException("writer");
            if (targetSize.Width <= 0) throw new ArgumentOutOfRangeException("targetSize", "The page width must be larger than 0.");
            if (targetSize.Height <= 0) throw new ArgumentOutOfRangeException("targetSize", "The page height must be larger than 0.");

            _baseGraphics = baseGraphics;
            _defaultFormat = new StringFormat();
            _documentResources = new Dictionary<ScopedResource, PdfResourceReference>();
            _flowData = new List<TextFlow>();
            
            _flowEngine = new TextFlowEngine();
            _flowEngine.Flow += new TextFlowEventHandler(FlowEngine_Flow);
            
            _state = new GraphicsState(writer);
            _stateStack = new Stack<GraphicsState>();

            _target = target;
            _targetResources = new List<PdfResourceReference>();

            _writer = writer;
            _writer.WriteCustomResource += new CustomResourceEventHandler(Writer_WriteCustomResource);
            
            SetupDefaults();

            SetupWriter(new Rectangle(0, 0, targetSize.Width, targetSize.Height));
        }
        #endregion
    } // Graphics
}
